<?php

namespace IdaasClient\Utils;

use App\Utils\UserUtil;
use Grpc\Base\Idaas\V1\CheckResourceRequest;
use Grpc\Base\Idaas\V1\CheckResourceResponse;
use Grpc\Base\Idaas\V1\GetPoolByPoolIdRequest;
use Grpc\Base\Idaas\V1\GetPoolByPoolIdResponse;
use Grpc\Base\Idaas\V1\GetSwitchedSupplierRequest;
use Grpc\Base\Idaas\V1\GetSwitchedSupplierResponse;
use Grpc\Base\Idaas\V1\GetUserPermissionsRequest;
use Grpc\Base\Idaas\V1\GetUserPermissionsResponse;
use Grpc\Base\Idaas\V1\IdaasClient;
use Grpc\Base\Idaas\V1\ParseAccessTokenRequest;
use Grpc\Base\Idaas\V1\ParseAccessTokenResponse;
use Hyperf\Grpc\StatusCode;
use Hyperf\GrpcClient\Exception\GrpcClientException;
use Hyperf\Cache\Annotation\Cacheable;
use Hyperf\GrpcServer\Exception\GrpcException;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Codec\Json;
use Hyperf\Utils\Network;
use Hyperfx\Utils\NetworkUtil;
use Hyperfx\Utils\ProtobufUtil;
use Hyperfx\Utils\SessionCacheUtil;

class IdaasClientUtil {

    #[Cacheable(prefix: 'idaas-parse-token', ttl: 600, value: '#{accessTokenMd5}')]
    private function _parseToken(string $accessToken, string $csrfToken, string $accessTokenMd5, string $poolId, string $clientIp) {
        $client = IdaasClient::create();
        $request = new ParseAccessTokenRequest();
        $request->setAccessToken($accessToken);
        $request->setCsrfToken($csrfToken);
        $request->setPoolId($poolId);
        $request->setClientIp($clientIp);
        /* @var $response ParseAccessTokenResponse|string */
        [$response, $status] = $client->ParseAccessToken($request);
        if ($status != StatusCode::OK) {
            throw new GrpcClientException($response, $status);
        }
        return [
            'user_id' => $response->getUserId(),
            'partner_id' => $response->getPartnerId(),
            'current_partner_id' => $response->getCurrentPartnerId(),
            'username' => $response->getUsername(),
            'show_name' => $response->getShowName(),
            'client_ip' => $response->getClientId(),
            'pool_id' => $response->getPoolId(),
            'expire_time' => $response->getExpireTime(),
            'session_id' => $response->getSessionId(),
            'user_type' => $response->getUserType(),
            'bind_data_id' => $response->getBindDataId(),
            'dept_id' => method_exists($response, 'getDeptId') ? $response?->getDeptId() : '',
        ];
    }

    /**
     * 解析用户TOKEN
     */
    public function parseToken(string $accessToken, string $csrfToken, string $poolId) {
        $ip = NetworkUtil::getClientIp();
        $resp = $this->_parseToken($accessToken, $csrfToken, md5(sprintf('%s-%s-%s', $accessToken, $poolId, $ip)), $poolId, NetworkUtil::getClientIp());
        // 使用使用了缓存，需要重新计算下
//        if (0 !== strcmp(config('app_env'), 'local') && !empty($resp['client_ip']) && 0 !== strcmp($ip, $resp['client_ip'])) {
//            throw new GrpcException('用户凭证已失效，请重新登录', StatusCode::UNAUTHENTICATED);
//        }
//        if ($resp['pool_id'] != $poolId) {
//            throw new GrpcException('用户池不匹配', StatusCode::UNAUTHENTICATED);
//        }
        if ($resp['expire_time'] < time()) {
            throw new GrpcException('用户凭证已过期，请重新登录', StatusCode::UNAUTHENTICATED);
        }
        return $resp;
    }

    /**
     * 获取用户权限
     */
    #[Cacheable(prefix: 'idaas-user-permission', ttl: 600, value: '#{poolId}-#{projectId}-#{appId}-#{userId}-#{sessionId}')]
    public function getUserPermissions(string $poolId, string $projectId, string $appId, string $appSecret, string $userId, string $sessionId) {
        $client = IdaasClient::create();
        $request = new GetUserPermissionsRequest();
        $request->setPoolId($poolId);
        $request->setUserId($userId);
        $request->setProjectId($projectId);
        $request->setAppId($appId);
        $request->setAppSecret($appSecret);
        /* @var $response GetUserPermissionsResponse|string */
        [$response, $status] = $client->GetUserPermissions($request);
        if ($status != StatusCode::OK) {
            throw new GrpcClientException($response, $status);
        }
        $items = $response->getItems();
        $result = [];
        /* @var $item GetUserPermissionsResponse\Item */
        foreach ($items as $item) {
            $result[] = [
                'dept_ids' => ProtobufUtil::repeatedToStringArray($item->getDeptIds()),
                'permissions' => ProtobufUtil::repeatedToStringArray($item->getPermissions()),
                'resources' => Json::decode($item->getResources()),
                'role_ids' => ProtobufUtil::repeatedToStringArray($item->getRoleIds()),
            ];
        }
        return $result;
    }

    /**
     * 获取池信息
     */
    #[Cacheable(prefix: 'idaas-pool', ttl: 600)]
    public function getPool(string $poolId, string $domain = '') {
        $client = IdaasClient::create();
        $request = new GetPoolByPoolIdRequest();
        $request->setPoolId($poolId);
        method_exists($request, 'setDomain') && $request->setDomain($domain);
        /* @var $response GetPoolByPoolIdResponse|string */
        [$response, $status] = $client->GetPoolByPoolId($request);
        if ($status != StatusCode::OK) {
            throw new GrpcClientException($response, $status);
        }
        return [
            'pool_id' => $response->getPoolId(),
            'cookie_domain' => $response->getCookieDomain(),
            'pool_name' => $response->getPoolName(),
            'pool_desc' => $response->getPoolDesc(),
            'access_token_key' => sprintf('%s-ACCESS-TOKEN', $response->getCookieTokenPrefix()),
            'refresh_token_key' => sprintf('%s-REFRESH-TOKEN', $response->getCookieTokenPrefix())
        ];
    }


    /**
     * 获取供应商信息
     */
    public function getSwitchedSupplier(string $poolId, int $currentPartnerId, string $userId, string $sessionId) {
        return SessionCacheUtil::get(function (string $cacheId) use ($poolId, $currentPartnerId, $userId, $sessionId) {
            $result = $this->getSwitchedSupplierHasCache($poolId, $currentPartnerId, $userId, $sessionId, $cacheId);
            return $result['supplier_id'];
        }, function () use ($poolId, $currentPartnerId, $userId, $sessionId) {
            $result = $this->_getSwitchedSupplier($poolId, $currentPartnerId, $userId, $sessionId);
            return $result['supplier_id'];
        });
    }

    /**
     * 获取供应商信息
     */
    #[Cacheable(prefix: 'idaas-switched-supplier-v1', ttl: 600)]
    public function getSwitchedSupplierHasCache(string $poolId, int $currentPartnerId, string $userId, $sessionId, string $cacheId) {
        return $this->_getSwitchedSupplier($poolId, $currentPartnerId, $userId, $sessionId);
    }

    private function _getSwitchedSupplier(string $poolId, int $currentPartnerId, string $userId, string $sessionId) {
        $client = IdaasClient::create();
        $request = new GetSwitchedSupplierRequest();
        $request->setPoolId($poolId);
        $request->setUserId($userId);
        $request->setPartnerId($currentPartnerId);
        method_exists($request, 'setSessionId') && $request->setSessionId($sessionId);
        /* @var $response GetSwitchedSupplierResponse|string */
        [$response, $status] = $client->GetSwitchedSupplier($request);
        if ($status != StatusCode::OK) {
            throw new GrpcClientException($response, $status);
        }
        return [
            'supplier_id' => $response->getSupplierId(),
        ];
    }

    /**
     * 检查资源
     */
    #[Cacheable(prefix: 'IdaasClientUtil:checkResource', ttl: 600)]
    public function checkResource(int $partnerId, string $siteId, string $namespace) {
        $client = IdaasClient::create();
        $request = new CheckResourceRequest();
        $request->setPartnerId($partnerId);
        $request->setSiteId($siteId);
        $request->setNamespace($namespace);
        /* @var $response CheckResourceResponse|string */
        [$response, $status] = $client->CheckResource($request);
        if ($status != StatusCode::OK) {
            throw new GrpcClientException($response, $status);
        }
        return true;
    }
}